import os
import cv2
import yaml
import base64
import random
import logging
import logging.handlers
import threading
import numpy as np
from time import sleep
from datetime import datetime, timedelta, time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, ElementNotInteractableException, StaleElementReferenceException

filePath = os.path.dirname(__file__)
logfilename = os.path.join(
    filePath, 'new_log', 'my')

logging.basicConfig(level=logging.INFO,
                    filename=logfilename,
                    filemode="w",
                    format="%(asctime)s - %(name)s - %(levelname)-9s - %(filename)-8s : %(lineno)s line - %(threadName)s - %(message)s",
                    datefmt="%Y-%m-%d %H:%M:%S")
file_handler = logging.handlers.TimedRotatingFileHandler(
    logfilename, when='D', interval=1,  backupCount=7, atTime=time(0, 0, 0, 0))
logging.getLogger().addHandler(file_handler)


def clickable_element(x: WebDriver, by=By.ID, value=None):
    logging.info("by:%s, value: %s, want to click", by, value)
    return [element for element in x.find_elements(by, value) if EC.element_to_be_clickable(element)]


def get_track(distance):  # distance为传入的总距离
    # 移动轨迹
    track = []
    # 当前位移
    current = 0
    # 减速阈值
    mid = distance * 4 / 5
    # 计算间隔
    t = 0.2
    # 初速度
    v = distance / 3

    while current < distance:
        if current < mid:
            # 加速度为2
            a = 4
        else:
            # 加速度为-2
            a = -3
        v0 = v
        # 当前速度
        v = v0 + a * t
        # 移动距离
        move = v0 * t + 1 / 2 * a * t * t
        # 当前位移
        current += move
        # 加入轨迹
        track.append(round(move))
    logging.info(distance)
    logging.info(track)
    return track


def identify_gap(bg_img, tp_img, out):
    '''
    bg: 背景图片
    tp: 缺口图片
    out:输出图片
    '''
    # 识别图片边缘
    bg_edge = cv2.Canny(bg_img, 100, 200)
    tp_edge = cv2.Canny(tp_img, 100, 200)

    # 转换图片格式
    bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
    tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)

    # 缺口匹配
    res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 寻找最优匹配

    # 绘制方框
    th, tw = tp_pic.shape[:2]
    tl = max_loc  # 左上角点的坐标
    br = (tl[0] + tw, tl[1] + th)  # 右下角点的坐标
    cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2)  # 绘制矩形
    cv2.imwrite(out, bg_img)  # 保存在本地

    # 返回缺口的X坐标
    return tl[0]


class RobCourt(object):
    def __init__(self, username, password, when_to_start, during_time, court_no):
        chrome_options = Options()
        chrome_options.add_argument('--headless')
        chrome_options.add_argument('--no-sandbox')
        chrome_options.add_argument('--disable-dev-shm-usage')

        self.driver = webdriver.Chrome(os.path.join(
            filePath, 'chromedriver'), chrome_options=chrome_options)
        self.block_num = 8
        self.court_url = "https://cgyy.buaa.edu.cn/venue/venue-reservation/"
        if court_no == 0:
            self.court_url = self.court_url + "38"
            self.block_num = 12
        elif court_no == 1:
            self.court_url = self.court_url + "39"
        else:
            self.court_url = self.court_url + "57"
        self.username = username
        self.passwd = password
        self.login_url = "https://cgyy.buaa.edu.cn/venue-server/sso/manageLogin"
        self.during_time = during_time
        self.when_to_start = when_to_start
        self.jump_times = (self.when_to_start - 7) // 5
        self.block_column = (self.when_to_start - 7) % 5
        pre_shuffle = list(range(self.block_num))
        random.shuffle(pre_shuffle)
        self.left_block = [5 * i + self.block_column for i in pre_shuffle]
        logging.info('场地:[%s]' % ','.join([str(i) for i in self.left_block]))

    def move_to_gap(self, slider, distance):  # slider是要移动的滑块,tracks是要传入的移动轨迹
        ActionChains(self.driver).click_and_hold(slider).perform()
        ActionChains(self.driver).move_by_offset(
            xoffset=distance, yoffset=0).perform()
        ActionChains(self.driver).release().perform()
        logging.info("移动滑块 %d" % distance)

    def find_by_xpath(self, xpath, need_click=False):
        try:
            if need_click:
                node = WebDriverWait(self.driver, 2).until(
                    EC.element_to_be_clickable((By.XPATH, xpath)))
            else:
                node = WebDriverWait(self.driver, 2).until(
                    EC.visibility_of_element_located((By.XPATH, xpath)))
            logging.info("xpath结点存在 %s" % (xpath))
            return node
        except:
            raise

    def find_by_classname(self, classname, need_click=False):
        try:
            class_n = classname.split()
            by = By.CLASS_NAME
            if len(class_n) > 1:
                classname = '.' + '.'.join(class_n)
                by = By.CSS_SELECTOR
            nodes = WebDriverWait(self.driver, 2).until(
                EC.visibility_of_any_elements_located((by, classname)))
            if need_click:
                nodes = WebDriverWait(self.driver, 2).until(
                    lambda x: clickable_element(x, by, classname))
            logging.info("classname结点存在 %s %d" % (classname, len(nodes)))
            if len(nodes) == 0:
                raise NoSuchElementException('节点 %s 不存在' % classname)
            elif len(nodes) == 1:
                return nodes[0]
            else:
                return nodes
        except:
            raise

    def choose_block(self, nodes):
        try:
            free_cnt = 0
            for i in self.left_block:
                logging.info("%d点 %d号 状态 %s" % (self.when_to_start,
                             i // 5 + 1, nodes[i].get_attribute('class')))
                if 'free' in nodes[i].get_attribute('class'):
                    free_cnt = free_cnt + 1
                    logging.info("预定了 %d点 开始 %d 号" %
                                 (self.when_to_start, i // 5 + 1))
                    self.reserve_time = [self.when_to_start]
                    self.during_time = self.during_time - 1
                    if self.during_time > 0 and 'free' in nodes[i + 1].get_attribute('class'):
                        nodes[i + 1].click()
                        logging.info("预定了 %d点 开始 %d 号" %
                                     (self.when_to_start + 1, (i + 1) // 5 + 1))
                        self.during_time = self.during_time - 1
                    break
            if self.during_time > 0:
                for i in self.left_block:
                    logging.info("%d点 %d号 状态 %s" % (
                        self.when_to_start + 1, (i + 1) // 5 + 1, nodes[i + 1].get_attribute('class')))
                    if 'free' in nodes[i + 1].get_attribute('class'):
                        free_cnt = free_cnt + 1
                        nodes[i + 1].click()
                        logging.info("预定了 %d点 开始 %d 号" %
                                     (self.when_to_start + 1, (i + 1) // 5 + 1))
                        self.during_time = self.during_time - 1
                        break
            if free_cnt == 0:
                raise ValueError("没有空场地了")
        except IndexError as e:
            raise IndexError('选择场地时数组越界')
        except Exception as e:
            raise

    def move_verify(self):
        t = datetime.now()
        bg_ = base64.b64decode(self.find_by_classname(
            'verify-img-panel').find_element_by_tag_name('img').get_attribute('src')[22:])
        tp_ = base64.b64decode(self.find_by_classname(
            'verify-sub-block').find_element_by_tag_name('img').get_attribute('src')[22:])
        bg = cv2.imdecode(np.frombuffer(bg_, dtype=np.uint8), flags=1)
        tp = cv2.imdecode(np.frombuffer(tp_, dtype=np.uint8), flags=1)
        distance = identify_gap(bg, tp, os.path.join(
            filePath, 'new_log', '%s-%d-%d.png' % (threading.current_thread().getName(), t.month, t.day)))
        tracks = get_track(distance)
        slider = self.find_by_classname('verify-move-block')
        self.move_to_gap(slider, sum(tracks) + 3)

    def checkReserved(self):
        notice_nodes = self.find_by_classname('ivu-message')
        if isinstance(notice_nodes, list):
            for i, node in enumerate(notice_nodes):
                try:
                    logging.info('notice: %d, class: %s' %
                                 (i, node.get_attribute('class')))
                except Exception as e:
                    logging.error(e)
                    pass
        else:
            logging.info('notice node, class: %s' %
                         (notice_nodes.get_attribute('class')))

    def login(self):
        self.driver.get(self.login_url)
        sleep(1)
        self.driver.switch_to.frame(
            self.driver.find_element(By.ID, 'loginIframe'))
        self.driver.find_element(By.ID, 'unPassword').send_keys(self.username)
        self.driver.find_element(By.ID, 'pwPassword').send_keys(self.passwd)
        self.find_by_xpath(
            '//*[@id="content-con"]/div[1]/div[7]', True).click()
        sleep(1)
        logging.info("输入了账号和密码")
        #  检验是否有弱密码忽略界面
        if self.driver.current_url == "https://sso.buaa.edu.cn/login":
            self.driver.find_element(By.LINK_TEXT, "忽略一次").click()
        for i in range(10):
            if self.driver.current_url != "https://cgyy.buaa.edu.cn/venue/home":
                sleep(1)
            else:
                return 1
        logging.error("登陆过于频繁，访问被拒绝了")
        return 0

    def start_reserve(self):
        if self.login():
            logging.info("登陆成功，等待七点之后再进入")
            c = datetime.now()
            c = datetime(c.year, c.month, c.day, 7, 0, 0)
            while datetime.now() < c:
                logging.info("等待进入")
                sleep(1)
            for rep in range(5):
                logging.info("第 %d 次访问网页%s" % (rep + 1, self.court_url))
                self.driver.get(self.court_url)
                try:
                    logging.info("点击关闭按钮")
                    self.find_by_xpath(
                        '/html/body/div[7]/div[2]/div/div/div/div[2]/button', True).click()
                except Exception as e:
                    pass

                try:
                    logging.info("两次下一页")
                    self.driver.execute_script(
                        "for(var i=0;i<4;i++){document.getElementsByClassName('btnMaring ivu-btn ivu-btn-primary ivu-btn-icon-only')[1].click();}")
                    time_node = self.driver.execute_script(
                        "return document.getElementsByClassName('ivu-date-picker-cells-cell ivu-date-picker-cells-cell-selected')[0].textContent")
                    logging.info("判断是否是想要预约的时间 %s" % time_node)
                    for j in range(self.jump_times):
                        self.find_by_classname('pull-right', True)[0].click()
                    nodes = self.find_by_classname('position', True)
                    logging.info("开始选择场地")
                    self.choose_block(nodes)
                    logging.info("处理同意复选框")
                    checkbox_node = self.find_by_classname(
                        'ivu-checkbox', True)
                    if checkbox_node.get_attribute('class') == 'ivu-checkbox':
                        checkbox_node.click()
                    logging.info("点击我要预约")
                    self.find_by_xpath(
                        '/html/body/div[1]/div/div/div[3]/div[2]/div/div[2]/div[5]/div/div[2]', True).click()
                    logging.info("选择同伴")
                    self.find_by_xpath(
                        '/html/body/div[1]/div/div/div[3]/div[2]/div/div[2]/form/div/div[2]/div/div/label[1]', True).click()
                    logging.info("提交订单")
                    self.find_by_classname('payHandleItem', True)[1].click()
                    for i in range(2):
                        logging.info("第%d次验证" % (i + 1))
                        self.move_verify()
                        res_class = WebDriverWait(
                            self.driver,
                            2).until(lambda x: x.find_element(By.XPATH, '/html/body/div[1]/div[1]/div/div[3]/div[2]/div/div[2]/div[2]/div/div[2]/div/div[1]/div/span')).get_attribute('class')
                        if 'err' in res_class:
                            logging.error('验证失败')
                        elif 'suc' in res_class:
                            logging.info('验证成功')
                            success_id_list.add(
                                self.username + " " + self.passwd)
                            self.checkReserved()
                            return
                except ValueError as e:
                    logging.error(e)
                    break
                except IndexError as e:
                    logging.error(e)
                except NoSuchElementException as e:
                    logging.error(e)
                except ElementNotInteractableException as e:
                    logging.error(e)
                except StaleElementReferenceException as e:
                    logging.error(e)
                except Exception as e:
                    logging.error(e)
        else:
            logging.error("登陆失败")


def funwrapper(username, password, start, during, court_no):
    RobCourt(username=username, password=password, when_to_start=start,
             during_time=during, court_no=court_no).start_reserve()


def parseConfig(config):
    cur_weekday = str(datetime.now().weekday() + 1)
    args = []
    for user in config.get('users'):
        (username, info), = user.items()
        passwd = info['passwd']
        data = info['data']
        for item in data:
            if cur_weekday in item['weekday']:
                args.append(
                    (username, passwd, item['start'], item['during'], item['court_no']))
    return config.get('threadnum'), args


if __name__ == '__main__':
    success_id_list = set()
    logging.info("主线程开始")
    with open(os.path.join(filePath, 'config.yaml')) as f:
        config = yaml.load(f.read())
        threadnum, args = parseConfig(config)
        thread_list = []
        for argv in args:
            for i in range(threadnum):
                thread_list.append(threading.Thread(
                    target=funwrapper, name=argv[0] + '_' + str(i), args=argv))
        for thr in thread_list:
            thr.start()
        for thr in thread_list:
            thr.join()
    logging.info("主线程结束")

    if len(success_id_list) > 0:
        desp = '\n\n'.join(list(success_id_list))
        data = {
            "title": "成功预定",
            "desp":  desp,
            "short": desp,
            "channel": 9
        }
        import requests
        logging.error(requests.post(
            '', data=data).text)
        logging.error(requests.post(
            '', data=data).text)
